package backend;

import backend.inst.MipsComment;
import backend.inst.MipsInst;
import backend.inst.MipsLabel;
import backend.value.*;
import middleware.llvm.BasicBlock;
import middleware.llvm.Module;
import middleware.llvm.enums.CmpType;
import middleware.llvm.enums.IRType;
import middleware.llvm.instruction.*;
import middleware.llvm.value.*;

import java.util.*;
import java.util.stream.Stream;

/**
 * 将 IR 转换为目标代码的翻译器
 *
 * @author 杨凯伟
 * @date 2024/10/30
 */
public class Translator {
    private final Target asmTarget = new Target();
    private final ValueManager valueManager = new ValueManager();
    private TemRegisterPool tempRegisterPool = new TemRegisterPool(asmTarget, Stream.of("t0", "t1", "t2", "t3", "t4", "t5", "t6").map(Register.REGISTER_MAP::get).toList());
    private int memorySizeForLocal = 0;
    private final Map<Value, Register> registerTempMap = new HashMap<>();
//    private String

    public Target getAsmTarget() {
        return asmTarget;
    }

    public void translate(Module irModule) {
//        for (var globalVal : irModule.getGlobalValues()) {
//            translateGlobalValue(globalVal);
//        }
//
//        for (var func : irModule.getFunctions()) {
//            translateFunction(func);
//        }
        irModule.getGlobalValues().forEach(this::translateGlobalValue);
        irModule.getFunctions().forEach(this::translateFunction);
    }

    /**
     * 处理全局值的翻译，生成相应的数据段
     *
     * @param irGlobalValue
     */
//    private void translateGlobalValue(GlobalValue irGlobalValue) {
//        var initVals = irGlobalValue.getInitVals();
//
//        Data newDataEntry;
//        if (initVals.isEmpty()) {
//            if (irGlobalValue.getType() instanceof ArrayIRType arrayType) {
//                newDataEntry = new Data(irGlobalValue.getLabelName().substring(1), "space", List.of(4 * arrayType.getTotalSize()));
//            } else {
//                newDataEntry = new Data(irGlobalValue.getLabelName().substring(1), "word", List.of(0));
//            }
//        } else {
//            newDataEntry = new Data(irGlobalValue.getLabelName().substring(1), "word", Arrays.asList(initVals.toArray()));
//        }
//
//        asmTarget.addData(newDataEntry);
//        valueManager.putGlobal(irGlobalValue, newDataEntry.getLabel());
//    }
    private void translateGlobalValue(GlobalValue irGlobalValue) {
        Data newDataEntry = createDataEntry(irGlobalValue);
        asmTarget.addData(newDataEntry);
        valueManager.putGlobal(irGlobalValue, newDataEntry.getLabel());
    }

    private Data createDataEntry(GlobalValue irGlobalValue) {
        List<Integer> initVals = irGlobalValue.getInitVals();
        String name = irGlobalValue.getName().substring(1);
        if (initVals.isEmpty()) {
            return createEmptyDataEntry(irGlobalValue, name);
        } else {
            return createInitializedDataEntry(name, initVals);
        }
    }

    private Data createEmptyDataEntry(GlobalValue irGlobalValue, String name) {
        if (irGlobalValue.getType() instanceof ArrayIRType arrayType) {
            int size = 4 * arrayType.getTotalSize();
            return new Data(name, "space", List.of(size));
        } else {
            return new Data(name, "word", List.of(0));
        }
    }

    private Data createInitializedDataEntry(String name, List<Integer> initVals) {
        return new Data(name, "word", Arrays.asList(initVals.toArray()));
    }

    /**
     * - 处理函数的翻译，生成相应的代码段（`.text`）。<br>
     * - 计算局部变量的内存大小，并调整栈指针（`sp`）。<br>
     * - 初始化临时寄存器池，并遍历函数的基本块进行翻译。
     *
     * @param irFunction
     */
//    private void translateFunction(FunctionIRType irFunction) {
//        asmTarget.addText(new TextLabel(irFunction.getLabelName().substring(1)));
//        var totalMemorySize = valueManager.putLocals(irFunction);
//        memorySizeForLocal = totalMemorySize - irFunction.calcParamSpace();
//
//        if (memorySizeForLocal > 0) {
//            var sp = Register.REGISTER_MAP.get("sp");
//            asmTarget.addText(new TextInst("addiu", sp, sp, new Immediate(-memorySizeForLocal)));
//        }
//
//        Set<Register> tempRegisters = new HashSet<>(
//                Stream.of("s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
//                                "t0", "t1", "t2", "t3", "t4", "t5", "t6")
//                        .map(Register.REGISTER_MAP::get)
//                        .toList());
//        valueManager.getRegistersInUse().forEach(tempRegisters::remove);
//        tempRegisterPool = new TempRegisterPool(asmTarget, new ArrayList<>(tempRegisters));
//
//        for (var block : irFunction.getBasicBlocks()) {
//            tempRegisterPool.reset();
//            registerTempMap.clear();
//            translateBasicBlock(block);
//        }
//
//        valueManager.clearLocals();
//    }
    private void translateFunction(FunctionIRType irFunction) {
        asmTarget.addCode(new MipsLabel(irFunction.getName().substring(1)));
        int totalMemorySize = valueManager.putLocals(irFunction);
        memorySizeForLocal = totalMemorySize - irFunction.calcParamSpace();

        adjustStackPointer(memorySizeForLocal);
        initializeTempRegisterPool();

        for (var block : irFunction.getBasicBlocks()) {
            resetForNewBlock();
            translateBasicBlock(block);
        }

        valueManager.clearLocals();
    }

    private void adjustStackPointer(int memorySizeForLocal) {
        if (memorySizeForLocal > 0) {
            Register sp = Register.REGISTER_MAP.get("sp");
            asmTarget.addCode(new MipsInst("addiu", sp, sp, new Immediate(-memorySizeForLocal)));
        }
    }

    private void initializeTempRegisterPool() {
        Set<Register> tempRegisters = new HashSet<>(getAvailableTempRegisters());
        valueManager.getRegistersInUse().forEach(tempRegisters::remove);
        tempRegisterPool = new TemRegisterPool(asmTarget, new ArrayList<>(tempRegisters));
    }

    private List<Register> getAvailableTempRegisters() {
        List<Register> registers = new ArrayList<>();
        for (String regName : Arrays.asList("s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "t0", "t1", "t2", "t3", "t4", "t5", "t6")) {
            registers.add(Register.REGISTER_MAP.get(regName));
        }
        return registers;
    }

    private void resetForNewBlock() {
        registerTempMap.clear();
        tempRegisterPool.reset();
    }

    private void translateBasicBlock(BasicBlock irBlock) {
        var label = new MipsLabel(buildBlockLabelName(irBlock));
        asmTarget.addCode(label);
        irBlock.getInstructions().forEach(this::translateInstruction);

    }

    private void translateInstruction(Instruction inst) {
        asmTarget.addCode(new MipsComment(inst));
        if (inst instanceof BinaryInstruction i) {//运算
            translateBinaryInst(i);
        } else if (inst instanceof BrInstruction i) {//跳转
            translateBrInst(i);
        } else if (inst instanceof CallInstruction i) {//函数调用
            translateCallInst(i);
        } else if (inst instanceof ICmpInstruction i) {//立即数比较
            translateICmpInst(i);
        } else if (inst instanceof LoadInstruction i) {//取值
            translateLoadInst(i);
        } else if (inst instanceof StoreInstruction i) {//赋值
            translateStoreInst(i);
        } else if (inst instanceof ReturnInstruction i) {//返回
            translateReturnInst(i);
        } else if (inst instanceof GetElementPtrInstruction i) {//取指针
            translateGetElementPtrInst(i);
        } else if (inst instanceof ZExtInstruction i) {//zero
            translateZExtInst(i);
        } else if (inst instanceof AllocaInstruction i) {//分配内存
            translateAllocaInst(i);
        }
        Register.freeTempRegs();
    }

    /**
     * @param inst
     */
    private void translateBinaryInst(BinaryInstruction inst) {
        var target = tryAllocTempRegisterForInst(inst);
        var left = tryGetValueFromTempReg(inst.getLeft());
        if (left instanceof Immediate) {
            asmTarget.addCode(new MipsInst("li", target, left));
            left = target;
        }
        var right = tryGetValueFromTempReg(inst.getRight());
        if (right instanceof Immediate) {
            var tmpReg = Register.allocTempReg();
            asmTarget.addCode(new MipsInst("li", tmpReg, right));
            right = tmpReg;
        }
        switch (inst.getOp()) {
            case ADD, SUB ->
                    asmTarget.addCode(new MipsInst(inst.getOp().name().toLowerCase() + "u", target, left, right));
            case MUL -> asmTarget.addCode(new MipsInst(inst.getOp().name().toLowerCase(), target, left, right));
            case SDIV -> {
                asmTarget.addCode(new MipsInst("div", left, right));
                asmTarget.addCode(new MipsInst("mflo", target));
            }
            case SREM -> {
                asmTarget.addCode(new MipsInst("div", left, right));
                asmTarget.addCode(new MipsInst("mfhi", target));
            }
            case AND -> asmTarget.addCode(new MipsInst("and", target, left, right));
            case OR -> asmTarget.addCode(new MipsInst("or", target, left, right));
        }
    }

    /**
     * 优化版二元运算：对于立即数除数进行优化---还存在小部分bug，等后续ac再考虑优化
     */
//    private void translateBinaryInst(BinaryInst inst) {
//        var left = tryGetTempRegister(inst.getLeft());
//        var right = tryGetTempRegister(inst.getRight());
//        var target = tryAllocTempRegisterForInst(inst);
//        if (left instanceof Immediate) {
//            asmTarget.addText(new TextInst("li", target, left));
//            left = target;
//        }
//        switch (inst.getOp()) {
//            case ADD -> asmTarget.addText(new TextInst("addu", target, left, right));
//            case SUB -> asmTarget.addText(new TextInst("subu", target, left, right));
//            case MUL -> asmTarget.addText(new TextInst("mul", target, left, right));
//            case SDIV -> {
//                if (right instanceof Immediate) {
//                    int divisor = ((Immediate) right).getValue();
//                    long magicNumber = calculateMagicNumber(divisor);
//                    if (Integer.bitCount(divisor) == 1) {//2的幂，直接右移
//                        asmTarget.addText(new TextInst("srl", target, left, new Immediate(Integer.numberOfTrailingZeros(divisor))));
//                    } else {
//                        int shiftAmount = 31 + Integer.numberOfTrailingZeros(divisor);
//                        var tempHi = Register.allocateTempRegister(); // High 32 bits
//                        var tempLo = Register.allocateTempRegister(); // Low 32 bits
//
//                        // Multiply and store the result in two registers
//                        asmTarget.addText(new TextInst("mul", tempLo, left, new Immediate((int) magicNumber)));
//                        asmTarget.addText(new TextInst("mfhi", tempHi)); // Move high 32 bits to tempHi
//
//                        // Shift right by the required amount
//                        asmTarget.addText(new TextInst("srl", tempLo, tempLo, new Immediate(shiftAmount % 32))); // Logical shift right
//                        asmTarget.addText(new TextInst("sll", tempHi, tempHi, new Immediate(shiftAmount / 32))); // Shift left to compensate
//                        asmTarget.addText(new TextInst("or", target, tempLo, tempHi)); // Combine the two parts
//
//                        // Adjust for negative numbers if necessary
////                        asmTarget.addText(new TextInst("bgez", left, new Label("positive"))); // Check if left is positive
////                        asmTarget.addText(new TextInst("addiu", target, target, new Immediate(-1))); // If negative, subtract 1
////                        asmTarget.addText(new TextLabel("positive"));
//                    }
//                } else {
//                    asmTarget.addText(new TextInst("div", left, right));
//                    asmTarget.addText(new TextInst("mflo", target));
//                }
//            }
//            case SREM -> {
//                if (right instanceof Immediate) {
//                    int divisor = ((Immediate) right).getValue();
//                    long magicNumber = calculateMagicNumber(divisor);
//                    if (Integer.bitCount(divisor) == 1) {//2的幂，直接右移
//                        asmTarget.addText(new TextInst("and", target, left, new Immediate(divisor - 1)));
//                    } else {
//                        int shiftAmount = 31 + Integer.numberOfTrailingZeros(divisor);
//
//                        var tempHi = Register.allocateTempRegister(); // High 32 bits
//                        var tempLo = Register.allocateTempRegister(); // Low 32 bits
//                        var quotient = Register.allocateTempRegister(); // Quotient
//
//                        // Multiply and store the result in two registers
//                        asmTarget.addText(new TextInst("mul", tempLo, left, new Immediate((int) magicNumber)));
//                        asmTarget.addText(new TextInst("mfhi", tempHi)); // Move high 32 bits to tempHi
//
//                        // Shift right by the required amount
//                        asmTarget.addText(new TextInst("srl", tempLo, tempLo, new Immediate(shiftAmount % 32))); // Logical shift right
//                        asmTarget.addText(new TextInst("sll", tempHi, tempHi, new Immediate(shiftAmount / 32))); // Shift left to compensate
//                        asmTarget.addText(new TextInst("or", quotient, tempLo, tempHi)); // Combine the two parts
//
//                        // Multiply quotient by divisor and subtract from original value
//                        asmTarget.addText(new TextInst("mul", tempLo, quotient, new Immediate(divisor)));
//                        asmTarget.addText(new TextInst("mfhi", tempHi)); // Move high 32 bits to tempHi
//                        asmTarget.addText(new TextInst("subu", target, left, tempLo));
//                        asmTarget.addText(new TextInst("subu", target, target, tempHi)); // Subtract high part if necessary
//                    }
//                } else {
//                    asmTarget.addText(new TextInst("div", left, right));
//                    asmTarget.addText(new TextInst("mfhi", target));
//                }
//            }
//            case AND -> asmTarget.addText(new TextInst("and", target, left, right));
//            case OR -> asmTarget.addText(new TextInst("or", target, left, right));
//        }
//    }
    private long calculateMagicNumber(int divisor) {
        if (divisor == 1) {
            return 1; // 除以1不需要任何操作
        } else if (divisor == 2) {
            return 1; // 除以2可以直接右移1位
        } else {
            return ((long) 1 << 32) / divisor + 1;
        }
    }


    private void translateReturnInst(ReturnInstruction inst) {
        if (inst.getValue() != null) {
            var v0 = Register.REGISTER_MAP.get("v0");
            var value = tryGetValueFromTempReg(inst.getValue());
            if (value instanceof Immediate) {
                asmTarget.addCode(new MipsInst("li", v0, value));
            } else {
                asmTarget.addCode(new MipsInst("move", v0, value));
            }
        }
        if (memorySizeForLocal > 0) {
            var sp = Register.REGISTER_MAP.get("sp");
            asmTarget.addCode(new MipsInst("addiu", sp, sp, new Immediate(memorySizeForLocal)));
        }
        asmTarget.addCode(new MipsInst("jr", Register.REGISTER_MAP.get("ra")));
    }

    private void translateLoadInst(LoadInstruction inst) {
        var ptr = inst.getPtr();

        if (ptr instanceof GetElementPtrInstruction) {
            handleGetElementPtrLoad(ptr, inst);
        } else {
            handleRegularLoad(ptr, inst);
        }
    }

    private void handleGetElementPtrLoad(Value ptr, LoadInstruction inst) {
        var ptrValue = tryGetValueFromTempReg(ptr);
        var registerPtr = convertToRegister(ptrValue);
        var target = tryAllocTempRegisterForInst(inst);

        asmTarget.addCode(new MipsInst("lw", target, new Offset(registerPtr, 0)));
    }

    private void handleRegularLoad(Value ptr, LoadInstruction inst) {
        var ptrValue = valueManager.getTargetValue(ptr);

        if (ptrValue instanceof Register regPtr) {
            registerTempMap.put(inst, regPtr);
            return;
        }

        var target = tryAllocTempRegisterForInst(inst);
        if (ptrValue instanceof Offset || ptrValue instanceof Label) {
            asmTarget.addCode(new MipsInst("lw", target, ptrValue));
        }
    }

    private void translateStoreInst(StoreInstruction inst) {
        if (inst.getValue() instanceof FunctionArgumentIRType) {
            return;
        }
        var ptr = inst.getPtr();
        var value = inst.getValue();

        if (ptr instanceof GetElementPtrInstruction) {
            handleGetElementPtrStore(ptr, value);
        } else {
            handleRegularStore(ptr, value);
        }
    }

    private void handleGetElementPtrStore(Value ptr, Value value) {
        var ptrValue = tryGetValueFromTempReg(ptr);
        var valueReg = tryGetValueFromTempReg(value);

        var registerPtr = convertToRegister(ptrValue);
        var registerValue = convertToRegister(valueReg);

        asmTarget.addCode(new MipsInst("sw", registerValue, new Offset(registerPtr, 0)));
    }

    private void handleRegularStore(Value ptr, Value value) {
        var ptrValue = valueManager.getTargetValue(ptr);
        var valueReg = tryGetValueFromTempReg(value);

        var registerValue = convertToRegister(valueReg);

        if (ptrValue instanceof Offset || ptrValue instanceof Label) {
            asmTarget.addCode(new MipsInst("sw", registerValue, ptrValue));
        } else if (ptrValue instanceof Register) {
            asmTarget.addCode(new MipsInst("move", (Register) ptrValue, registerValue));
        } else {
            throw new RuntimeException("Impossible case");
        }
    }


    private void translateICmpInst(ICmpInstruction inst) {
        var target = tryAllocTempRegisterForInst(inst);
        var left = tryGetValueFromTempReg(inst.getLeft());
        if (left instanceof Immediate) {
            asmTarget.addCode(new MipsInst("li", target, left));
            left = target;
        }
        var right = tryGetValueFromTempReg(inst.getRight());
        if (right instanceof Immediate) {
            var tmpReg = Register.allocTempReg();
            asmTarget.addCode(new MipsInst("li", tmpReg, right));
            right = tmpReg;
        }

        String instName = getInstructionName(inst.getCond());

        asmTarget.addCode(new MipsInst(instName, target, left, right));
    }

    private String getInstructionName(CmpType cond) {
        return switch (cond) {
            case EQ -> "seq";
            case NEQ -> "sne";
            case SGE -> "sge";
            case SGT -> "sgt";
            case SLE -> "sle";
            case SLT -> "slt";
        };
    }

    private void translateBrInst(BrInstruction inst) {
        var nextBlock = inst.getBasicBlock().getNextBasicBlock();

        if (inst.getCond() != null) {
            handleConditionalBranch(inst, nextBlock);
        } else {
            handleUnconditionalBranch(inst, nextBlock);
        }
    }

    private void handleConditionalBranch(BrInstruction inst, BasicBlock nextBlock) {
        var cond = tryGetValueFromTempReg(inst.getCond());
        var registerCond = convertToRegister(cond);
        var falseBranch = inst.getFalseBranch();
        var falseBranchName = buildBlockLabelName(falseBranch);
        var trueBranch = inst.getTrueBranch();
        var trueBranchName = buildBlockLabelName(trueBranch);
        if (nextBlock != falseBranch) {
            asmTarget.addCode(new MipsInst("beqz", registerCond, new Label(falseBranchName)));
        }
        if (nextBlock != trueBranch) {
            asmTarget.addCode(new MipsInst("bnez", registerCond, new Label(trueBranchName)));
        }
    }

    private void handleUnconditionalBranch(BrInstruction inst, BasicBlock nextBlock) {
        var destBranch = inst.getDest();
        var destBranchName = buildBlockLabelName(destBranch);
        if (nextBlock != destBranch) {
            asmTarget.addCode(new MipsInst("j", new Label(destBranchName)));
        }
    }


    private void translateCallInst(CallInstruction inst) {
        var func = inst.getFunc();

        if (func.equals(FunctionIRType.BUILD_IN_PUTINT) || func.equals(FunctionIRType.BUILD_IN_PUTCH)) {
            handlePutFuncCall(inst, func);
        } else if (func.equals(FunctionIRType.BUILD_IN_GETINT)) {
            handleGetIntFuncCall(inst);
        } else if (func.equals(FunctionIRType.BUILD_IN_GETCHAR)) {
            handleGetCharFuncCall(inst);
        } else {
            translateCommonFuncCall(inst);
        }
    }

    /**
     * 打印printf函数
     *
     * @param inst
     * @param func
     */
    private void handlePutFuncCall(CallInstruction inst, FunctionIRType func) {
//        asmTarget.addData();

        asmTarget.addCode(new MipsInst("li", Register.REGISTER_MAP.get("v0"), new Immediate(func == FunctionIRType.BUILD_IN_PUTINT ? 1 : 11)));

        var inputVal = inst.getParams().get(0);
        var inputTargetValue = tryGetValueFromTempReg(inputVal);

        var a0 = Register.REGISTER_MAP.get("a0");
        var t7 = Register.REGISTER_MAP.get("t7");
        asmTarget.addCode(new MipsInst("move", t7, a0));

        if (inputTargetValue instanceof Immediate) {
            asmTarget.addCode(new MipsInst("li", a0, inputTargetValue));
        } else {
            asmTarget.addCode(new MipsInst("move", a0, inputTargetValue));
        }
        asmTarget.addCode(new MipsInst("syscall"));
        asmTarget.addCode(new MipsInst("move", a0, t7));
    }

    private void handleGetIntFuncCall(CallInstruction inst) {
        asmTarget.addCode(new MipsInst("li", Register.REGISTER_MAP.get("v0"), new Immediate(5)));
        asmTarget.addCode(new MipsInst("syscall"));

        var target = tryAllocTempRegisterForInst(inst);
        asmTarget.addCode(new MipsInst("move", target, Register.REGISTER_MAP.get("v0")));
    }

    private void handleGetCharFuncCall(CallInstruction inst) {
        asmTarget.addCode(new MipsInst("li", Register.REGISTER_MAP.get("v0"), new Immediate(12)));
        asmTarget.addCode(new MipsInst("syscall"));

        var target = tryAllocTempRegisterForInst(inst);
        asmTarget.addCode(new MipsInst("move", target, Register.REGISTER_MAP.get("v0")));
    }


    private void translateCommonFuncCall(CallInstruction inst) {
        var func = inst.getFunc();
        var sp = Register.REGISTER_MAP.get("sp");

        List<Register> registerToReserve = new ArrayList<>();
        registerToReserve.addAll(Stream.of("ra").map(Register.REGISTER_MAP::get).toList());
        registerToReserve.addAll(valueManager.getRegistersInUse());

        int registerByteSize = 4 * registerToReserve.size();
        int paramByteSize = func.calcParamSpace();
        int newAllocByteSize = registerByteSize + paramByteSize;

        tempRegisterPool.writeBackToMemoryForAll();

        asmTarget.addCode(new MipsInst("addiu", sp, sp, new Immediate(-newAllocByteSize)));

        reserveRegistersInFuncCall(registerToReserve, paramByteSize);

        if (paramByteSize > 0) {
            int base = 0;

            for (int paramCount = 0; paramCount < inst.getParams().size(); paramCount++, base += 4) {
                var param = inst.getParams().get(paramCount);
                var targetParam = valueManager.getTargetValue(param);

                if (registerTempMap.containsKey(param)) {
                    var reg = registerTempMap.get(param);
                    var savedRegisterOffset = registerToReserve.indexOf(reg) * 4 + paramByteSize;
                    targetParam = new Offset(sp, savedRegisterOffset);
                } else {
                    if (targetParam instanceof Offset offsetParam) { // sp has changed, so offset change as well
                        if (tempRegisterPool.getRegister(offsetParam) != null) {
                            targetParam = tempRegisterPool.getRegister(offsetParam);
                        } else {
                            targetParam = new Offset(offsetParam.getBase(), offsetParam.getOffset() + newAllocByteSize);
                        }
                    }
                }
                switch (paramCount) {
                    case 0, 1, 2, 3 -> {
                        var argReg = Register.REGISTER_MAP.get("a" + paramCount);
                        assignToRegister(argReg, targetParam);
                    }
                    default -> {
                        var registerParam = convertToRegister(targetParam);
                        asmTarget.addCode(new MipsInst("sw", registerParam, new Offset(sp, base)));
                        Register.freeTempRegs();
                    }
                }
            }
        }

        tempRegisterPool.reset();
        asmTarget.addCode(new MipsInst("jal", new Label(func.getName().substring(1))));
        recoverRegistersInFuncCall(registerToReserve, paramByteSize);
        asmTarget.addCode(new MipsInst("addiu", sp, sp, new Immediate(newAllocByteSize)));
        if (func.getRetType().getType() != IRType.VOID) {
            var target = tryAllocTempRegisterForInst(inst);
            asmTarget.addCode(new MipsInst("move", target, Register.REGISTER_MAP.get("v0")));
        }
    }

    private void assignToRegister(Register reg, TarValue value) {
        if (value instanceof Immediate) {
            asmTarget.addCode(new MipsInst("li", reg, value));
        } else if (value instanceof Offset) {
            asmTarget.addCode(new MipsInst("lw", reg, value));
        } else if (value instanceof Register) {
            asmTarget.addCode(new MipsInst("move", reg, value));
        }
    }

    private void reserveRegistersInFuncCall(List<Register> registersToReserve, int baseOffset) {
        var sp = Register.REGISTER_MAP.get("sp");
        int offset = 0;
        for (var register : registersToReserve) {
            asmTarget.addCode(new MipsInst("sw", register, new Offset(sp, baseOffset + offset)));
            offset = offset + 4;
        }
    }

    private void recoverRegistersInFuncCall(List<Register> registersToRecover, int baseOffset) {
        var sp = Register.REGISTER_MAP.get("sp");
        int offset = 0;
        for (var register : registersToRecover) {
            asmTarget.addCode(new MipsInst("lw", register, new Offset(sp, baseOffset + offset)));
            offset = offset + 4;
        }
    }

    /**
     * 获得数组元素值
     *
     * @param inst
     */
    private void translateGetElementPtrInst(GetElementPtrInstruction inst) {
        var base = inst.getElementBase();
        var offsets = inst.getOffsets();
        var dims = base.getType().getArrayDims();

        var target = tryAllocTempRegisterForInst(inst);
        Register registerBase = (Register) target; // registerBase is just target

        initializeBaseRegister(base, registerBase);
        processOffsets(offsets, dims, registerBase);
    }

    private void initializeBaseRegister(Value base, Register registerBase) {
        if (base instanceof LoadInstruction || base instanceof GetElementPtrInstruction) { // if is pointer
            var baseVal = tryGetValueFromTempReg(base);
            asmTarget.addCode(new MipsInst("move", registerBase, baseVal));
        } else { // if is address
            asmTarget.addCode(new MipsInst("la", registerBase, valueManager.getTargetValue(base)));
        }
    }

    private void processOffsets(List<Value> offsets, List<Integer> dims, Register registerBase) {
        var registerTemp = Register.allocTempReg();
        int currDim = 0;
        for (var offset : offsets) {
            if (offset instanceof ImmediateValue immediate && immediate.getValue() == 0) {
                currDim++;
                continue;
            }
            int memSize = calculateMemorySize(dims, currDim);
            var offsetVal = tryGetValueFromTempReg(offset);

            if (offsetVal instanceof Immediate) {
                asmTarget.addCode(new MipsInst("li", registerTemp, offsetVal));
                multiplyRegisterByMemorySize(registerTemp, memSize);
            } else {
                asmTarget.addCode(new MipsInst("mul", registerTemp, offsetVal, new Immediate(memSize)));
            }

            asmTarget.addCode(new MipsInst("addu", registerBase, registerBase, registerTemp));
            currDim++;
        }
    }

    private int calculateMemorySize(List<Integer> dims, int currDim) {
        int memSize = 4;
        for (int i = currDim; i < dims.size(); i++) {
            memSize *= dims.get(i);
        }
        return memSize;
    }

    private void multiplyRegisterByMemorySize(Register registerTemp, int memSize) {
        if (Integer.bitCount(memSize) == 1) { // 是2的幂 (memSize & (memSize - 1)) == 0
            asmTarget.addCode(new MipsInst("sll", registerTemp, registerTemp, new Immediate(Integer.numberOfTrailingZeros(memSize)))); // 几次幂, Math.log2();
        } else { // 不是2的幂
            asmTarget.addCode(new MipsInst("mul", registerTemp, registerTemp, new Immediate(memSize)));
        }
    }

    private void translateZExtInst(ZExtInstruction inst) {
        var value = tryGetValueFromTempReg(inst.getValue());
        var target = tryAllocTempRegisterForInst(inst);
        if (value instanceof Immediate) {
            asmTarget.addCode(new MipsInst("li", target, value));
        } else {
            asmTarget.addCode(new MipsInst("move", target, value));
        }
    }

    private void translateAllocaInst(AllocaInstruction inst) {
        var targetValue = valueManager.getTargetValue(inst);
        asmTarget.addCode(new MipsComment(inst.getName() + ": " + targetValue));
    }

    private static String buildBlockLabelName(BasicBlock block) {
        return block.getFunction().getName().substring(1) + "." + block.getName().substring(1);
    }

    private TarValue tryAllocTempRegisterForInst(Value inst) {
        if (registerTempMap.containsKey(inst)) {
            return registerTempMap.get(inst);
        }

        var instValue = valueManager.getTargetValue(inst);
        if (instValue instanceof Offset offsetInstValue) {
            return tempRegisterPool.allocTempRegister(offsetInstValue, true);
        } else {
            return instValue;
        }
    }

    private TarValue tryGetValueFromTempReg(Value value) {
        if (registerTempMap.containsKey(value)) {
            return registerTempMap.get(value);
        }

        TarValue targetValue = valueManager.getTargetValue(value);
        if (targetValue instanceof Offset offsetValue) {
            return tempRegisterPool.allocTempRegister(offsetValue, false);
        } else {
            return targetValue;
        }
    }

    private Register convertToRegister(TarValue targetValue) {
        if (isAddress(targetValue)) {
            Register newReg = Register.allocTempReg();
            asmTarget.addCode(new MipsInst("lw", newReg, targetValue));
            return newReg;
        } else if (isImmediate(targetValue)) {
            var newReg = Register.allocTempReg();
            asmTarget.addCode(new MipsInst("li", newReg, targetValue));
            return newReg;
        } else {
            return (Register) targetValue;
        }
    }

    private boolean isImmediate(TarValue value) {
        return value instanceof Immediate;
    }

    private boolean isAddress(TarValue value) {
        return value instanceof Offset || value instanceof Label;
    }
}
